import gtk
import itertools
from signaling import Signal, SValue, SList, SObject
from toolkit.dialogs import message_box, get_filename_for_open, get_input


def KeymapMixinMeta(name, bases, env):
    __gsignals__ = {
        "key_press_event" : "override",
        #"key_release_event" : "override",
    }
    base = bases[0]
    def do_key_press_event(self, evt):
        if self.owner:
            if self.owner.do_key_press_event(evt):
                return True
        if base.do_key_press_event(self, evt):
            return True
        return False
    #def do_key_release_event(self, evt):
    #    if base.do_key_release_event(self, evt):
    #        return True
    #    if self.owner:
    #        return self.owner.do_key_release_event(evt)
    #    return False
    env["owner"] = None
    env["__gsignals__"]  = __gsignals__
    env["do_key_press_event"] = do_key_press_event
    #env["do_key_release_event"] = do_key_release_event
    meta = type(base)
    return meta(name, bases, env)


def _layout_paned(pane, children):
    if len(children) == 1:
        return children[0]
    p = pane()
    p.pack1(children[0], resize = True)
    p.pack2(_layout_paned(pane, children[1:]), resize = True)
    return p

def _layout(box, pane, children):
    obj = box()
    if not children:
        return obj
    obj.pack_start(_layout_paned(pane, children))
    return obj

def HBox(*children):
    return _layout(gtk.HBox, gtk.HPaned, children)

def VBox(*children):
    return _layout(gtk.VBox, gtk.VPaned, children)

def HFixed(child):
    obj = gtk.HBox()
    obj.pack_start(child, expand = False, fill = False)
    return obj

def VFixed(child):
    obj = gtk.VBox()
    obj.pack_start(child, expand = False, fill = False)
    return obj

def Fixed(child):
    return VFixed(HFixed(child))

def MenuItem(name, action, keybinding = None, accel_group = None):
    obj = gtk.MenuItem(name)
    if action is not None:
        obj.connect("activate", action)
    if keybinding:
        key, mod = gtk.accelerator_parse(keybinding)
        if key == 0:
            raise ValueError("invalid key string: %r" % (keybinding,))
        obj.add_accelerator("activate", accel_group, key, mod, gtk.ACCEL_VISIBLE)
    return obj

def MenuSep():
    return gtk.SeparatorMenuItem()

def Menu(name, *children):
    obj = gtk.Menu()
    for child in children:
        obj.append(child)
    obj2 = gtk.MenuItem(name)
    obj2.set_submenu(obj)
    return obj2

def MenuBar(*menus):
    obj = gtk.MenuBar()
    for mitem in menus:
        obj.append(mitem)
    return VFixed(obj)

class _Window(gtk.Window):
    __metaclass__ = KeymapMixinMeta

def Window(title, child, owner = None):
    obj = _Window(gtk.WINDOW_TOPLEVEL)
    obj.owner = owner
    obj.set_title(title)
    obj.set_resizable(True)  
    obj.add(child)
    return obj

def Frame(title, child):
    obj = gtk.Frame(title)
    obj.add(child)
    obj.show()
    return obj

def Label(text):
    if isinstance(text, basestring):
        obj = gtk.Label(text)
    else:
        obj = gtk.Label(text.get())
        text.register(lambda v: obj.set_text(v.get()))
    return obj

class _Entry(gtk.Entry):
    __metaclass__ = KeymapMixinMeta

def TextLine(svalue, readonly = False, owner = None):
    obj = _Entry()
    obj.owner = owner
    obj.set_editable(not readonly)
    def user_change(obj):
        svalue.set(obj.get_text())
    obj.connect("changed", user_change)
    svalue.register(obj.set_text)
    return obj

#class _TextView(gtk.TextView):
#    __metaclass__ = KeymapMixinMeta

def TextBox(svalue):
    buf = gtk.TextBuffer()
    obj = _TextView(buf)
    svalue.register(lambda v: buf.set_text(v.get()))
    return Scrolled(obj)

def LineHighlightTextBox(svalue, slineno):
    print "foo"


#def LineHighlightTextBox(svalue, slineno):
#    buf = gtk.TextBuffer()
#    obj = gtk.TextView()
#    obj.set_editable(False)
#    highlighted_tag = buf.create_tag(background="lightblue")
#    last_tag_iter = [None]
#    def set_text(svalue):
#        obj.svalue.get()
#        last_tag_iter[0] = None
#    def set_highlight(slineno):
#        slineno.get()
#        if last_tag_iter:
#            before_iter, after_iter = last_tag_iter
#            buf.remove_tag(highlighted_tag, before_iter, after_iter)
#        before_iter = buf.get_iter_at_line_index(lineno, 0)
#        after_iter = buf.get_iter_at_line_index(lineno + 1, 0)
#        buf.apply_tag(highlighted_tag, before_iter, after_iter)
#        last_tag_iter[0] = (before_iter, after_iter)
#        mark = buf.create_mark('', before_iter)
#        obj.scroll_to_mark(mark, 0, use_align=True)
#    svalue.register(set_text)
#    slineno.register(set_highlight)
#    return Scrolled(obj)

class _ScrolledWindow(gtk.ScrolledWindow):
    __metaclass__ = KeymapMixinMeta

def Scrolled(child, owner = None):
    obj = _ScrolledWindow()
    obj.owner = owner
    obj.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
    obj.add(child)
    return obj

class ListModel(gtk.GenericTreeModel):
    def __init__(self, list, columns = (("Value", str),)):
        gtk.GenericTreeModel.__init__(self)
        self.list = list
        self.columns = []
        for name, type in columns:
            self.append_column(name, type)

    def append_column(self, title, type):
        column_id = len(self.columns) 
        cell = gtk.CellRendererText()
        column = gtk.TreeViewColumn(title)
        column.pack_start(cell, True)
        column.add_attribute(cell, 'text', column_id)
        column.set_sizing(gtk.TREE_VIEW_COLUMN_AUTOSIZE)
        column.set_resizable(True)
        self.columns.append((column, type))
        return column

    def on_get_flags(self):
        return gtk.TREE_MODEL_LIST_ONLY|gtk.TREE_MODEL_ITERS_PERSIST
    def on_get_n_columns(self):
        return len(self.columns)
    def on_get_column_type(self, n):
        return self.columns[n][1]
    def on_get_iter(self, path):
        if not self.list:
            return None
        return path[0]
    def on_get_path(self, rowref):
        return (rowref,)
    def on_iter_children(self, rowref):
        return on_iter_nth_child(rowref, 0)
    def on_iter_has_child(self, rowref):
        return False
    def on_iter_parent(self, child):
        return None
    
    def on_iter_n_children(self, rowref):
        if rowref:
            return 0
        return len(self.list)

    def on_get_value(self, rowref, column):
        try:
            return self.list[rowref]
        except IndexError:
            return None

    def on_iter_next(self, rowref):
        rowref += 1
        if rowref < 0 or rowref >= len(self.list):
            return None
        return rowref

    def on_iter_nth_child(self, rowref, n):
        if rowref or n < 0 or n >= len(self.list):
            return None
        return n

class _TreeView(gtk.TreeView):
    __metaclass__ = KeymapMixinMeta

def ListBox(list, cursor, search = True, owner = None):
    model = ListModel(list)
    def cursor_changed(obj):
        c = obj.get_cursor()[0]
        cursor.set(c)
    def update_list(*args):
        c = obj.get_cursor()[0]
        obj.freeze_child_notify()
        obj.set_model(None)
        obj.set_model(model)
        obj.thaw_child_notify()
        if c is not None:
            if c[0] >= len(list):
                c = (len(list) - 1,)
            obj.set_cursor(c)
    def update_cursor(cursor):
        obj.set_cursor(cursor.get())
    obj = _TreeView()
    obj.owner = owner
    obj.connect("cursor-changed", cursor_changed)
    obj.set_enable_search(search)
    list.register(update_list)
    cursor.register(update_cursor)
    for col, _ in model.columns:
        obj.append_column(col)
    obj.set_model(model)
    return Scrolled(obj, owner = owner)

def TreeBox(model, cursor, search = False, owner = None):
    list = SList(["hello", "world", "lala", "bloop", "zloop", "frrrr", "gtrrr"])
    return ListBox(list, cursor, search = search, owner = owner)

_actions_order = itertools.count()
_accel_group = gtk.AccelGroup()

def action(name, keybinding = None, doc = None):
    def deco(func):
        func._gtdom = dict(name = name, keybinding = keybinding, doc = doc, 
            order = _actions_order.next())
        return func
    return deco

class Module(object):
    TITLE = None
    owner = None
    
    def do_key_press_event(self, evt):
        key = (evt.keyval, evt.state)
        if key in self._keybindings:
            self._keybindings[key]()
            return True
        if self.owner:
            return self.owner.do_key_press_event(evt)
        return False
    
#    def do_key_release_event(self, evt):
#        if self.owner:
#            return self.owner.do_key_release_event(evt)
#        return False
    
    def _get_actions(self):
        actions = []
        for name in dir(self):
            obj = getattr(self, name, None)
            config = getattr(obj, "_gtdom", None)
            if not config:
                continue
            config["callback"] = obj
            actions.append(config)
        actions.sort(key = lambda config: config["order"])
        return actions
    
    def _get_title(self):
        if self.TITLE:
            title = self.TITLE
        else:
            title = self.__class__.__name__
            if title.lower().endswith("module"):
                title = title[:-6]
        return title
    
    def _render_actions_menu(self):
        actions = self._get_actions()
        if not actions:
            return None
        items = []
        def ignoreargs(f):
            def wrapper(*args):
                return f()
            return wrapper
        self._keybindings = {}
        items = []
        for config in actions:
            item = MenuItem(config["name"], ignoreargs(config["callback"]), 
                keybinding = config["keybinding"], accel_group = _accel_group)
            if config["keybinding"]:
                k = gtk.accelerator_parse(config["keybinding"])
                self._keybindings[k] = config["callback"]
            items.append(item)
        return MenuBar(Menu("Actions", *items))
    
    def render(self, owner):
        self.owner = owner
        menu = self._render_actions_menu()
        body = self._render(self)
        if menu:
            box = gtk.VBox()
            box.pack_start(menu, expand = False, fill = False)
            box.pack_start(body)
        else:
            box = body
        return Frame(self._get_title(), box)
    
    def _render(self, owner):
        raise NotImplementedError()

class Application(Module):
    def main(self):
        root = self.render()
        gtk.main()
    
    def on_close(self, *args):
        gtk.main_quit()
    
    def render(self):
        menu = self._render_actions_menu()
        body = self._render(self)
        if menu:
            box = gtk.VBox()
            box.pack_start(menu, expand = False, fill = False)
            box.pack_start(body)
        else:
            box = body
        win = Window(self._get_title(), box)
        win.connect("destroy", self.on_close)
        win.show_all()
        return win

#===============================================================================
# application code
#===============================================================================

class BookmarksModule(Module):
    def __init__(self):
        self.list = SList()
        self.cursor = SValue()
        self.jumpsig = Signal()
    
    @action("Jump", keybinding = "Return")
    def act_jump(self):
        index = self.cursor.get()
        if index is None:
            return
        self.jumpsig.notify(self.list[index[0]])
    
    @action("Delete", keybinding = "Delete")
    def act_delete(self):
        index = self.cursor.get()
        if index is None:
            return
        del self.list[index[0]]
    
    def add_bookmark(self, bookmark):
        if bookmark in self.list:
            return
        self.list.append(bookmark)
    
    def _render(self, owner):
        return ListBox(self.list, self.cursor, search = False, owner = owner)

class FilteringModule(Module):
    def __init__(self):
        self.list = SList()
        self.cursor = SValue()
    
    @action("Delete", keybinding = "Delete")
    def act_delete(self):
        pass
    
    def _render(self, owner):
        return ListBox(self.list, self.cursor, search = False, owner = owner)

class SearchModule(Module):
    def __init__(self):
        self.func_name = SValue()
        self.fwdsig = Signal()
        self.bwdsig = Signal()
    
    @action("Search forward", keybinding = "F3")
    def act_search_fwd(self):
        self.fwdsig.notify(self.func_name.get())

    @action("Search backward", keybinding = "<shift>F3")
    def act_search_bwd(self):
        self.bwdsig.notify(self.func_name.get())
    
    def _render(self, owner):
        return HBox(
            Label("Function search"), 
            TextLine(self.func_name),
        )

def x(*a):
    print "x"


class SourceModule(Module):
    def __init__(self):
        self.text = SValue()
        self.lineno = SValue()
        self.filename = SValue("")
    
    def jump(self, filename, lineno):
        if self.filename.get() != filename:
            self.filename.set(filename)
            try:
                f = open(filename)
                data = f.read()
                f.close()
            except EnvironmentError:
                data = "<unable to load file %r>" % (filename,)
        self.text.set(data)
        self.lineno.set(lineno)
    
    
    def _render(self, owner):
        print "hello"
        x(self.text, self.highlight)
        LineHighlightTextBox(self.text, self.highlight)
        print "goobye"
        
#        return VBox(
#            HBox(
#                Label("Filename:"),
#                Label(self.filename),
#            ),
#            LineHighlightTextBox(self.text, self.highlight)
#        )

class HistoryList(object):
    def __init__(self, size):
        assert size > 0
        self._size = size
        self.clear()
    def clear(self):
        self._list = [None] * self._size
        self._index = 0
    
    def remember(self, obj):
        self._list[self._index] = obj
        self._index = (self._index + 1) % self._size
        self._list[self._index] = None
    
    def next(self):
        index = (self._index + 1) % self._size
        value = self._list[index]
        if value is not None:
            self._index = index
        return value
    
    def prev(self):
        index = (self._index - 1) % self._size
        value = self._list[index]
        if value is not None:
            self._index = index
        return value

class TraceModule(Module):
    def __init__(self, tree):
        self.history = HistoryList(100)
        self.tree = tree
        self.cursor = SValue()
        self.bookmarksig = Signal()
    
    @action("Bookmark", keybinding = "b")
    def act_bookmark(self):
        cursor = self.cursor.get()
        if cursor is None:
            return
        self.bookmarksig.notify(cursor)
    
    @action("Expand", keybinding = "Right")
    def act_expand(self):
        print "expand"
    
    @action("Collapse", keybinding = "Left")
    def act_collapse(self):
        print "collapse"
    
    @action("History prev", keybinding = "bracketleft")
    def act_jump_prev(self):
        pos = self.history.prev()
        if pos is not None:
            self.cursor.set(pos)
    
    @action("History next", keybinding = "bracketright")
    def act_jump_next(self):
        pos = self.history.next()
        if pos is not None:
            self.cursor.set(pos)
    
    def jump(self, cursor):
        self.history.remember(self.cursor.get())
        self.cursor.set(cursor)
    
    def load_trace_file(self, fileprefix):
        self.history.clear()
    
    def _render(self, owner):
        obj = TreeBox(self.tree, self.cursor, search = False, owner = owner)
        return obj

class TraceReaderApp(Application):
    TITLE = "HPTrace Reader"
    
    def __init__(self):
        self.bookmarks = BookmarksModule()
        self.trace = TraceModule(None)
        self.filetering = FilteringModule()
        self.search = SearchModule()
        self.source = SourceModule()
        self._connect_signals()
    
    def _do_jump(self):
        self.history.remember(self.tree.cursor.get())
        self.trace.jump()
    
    def _connect_signals(self):
        self.bookmarks.jumpsig.register(self.trace.jump)
        self.trace.bookmarksig.register(self.bookmarks.add_bookmark)
    
    @action("Load", keybinding = "<ctrl>o")
    def act_load(self):
        fn = get_filename_for_open()
        if fn is None:
            return
        print fn
    
    def _render(self, owner):
        return HBox(
            VBox(
                self.search.render(owner),
                self.trace.render(owner),
                self.source.render(owner),
            ),
            VBox(
                self.bookmarks.render(owner),
                self.filetering.render(owner),
            )
        )



app = TraceReaderApp()
app.main()









